import {FrameBuffer} from "./output/FrameBuffer";
import {Vector4} from "./math/Vector4";
import {DrawLineUtil} from "./utils/DrawLineUtil";
import {Vector2} from "./math/Vector2";
import {ObjFile} from "./asserts/ObjFile";
import {MathUtil} from "./math/MathUtil";
import {Vector3} from "./math/Vector3";
import {DrawTriangleUtil} from "./utils/DrawTriangleUtil";
import {CanvasDrawContext} from "./CanvasDrawContext";
import {ZBuffer} from "./output/ZBuffer";
import {OrthographicCamera} from "./scene/OrthographicCamera";
import {Matrix4} from "./math/Matrix4";
import {RenderContext} from "./scene/RenderContext";
import {MeshProgram} from "./programs/MeshProgram";
import {TextureProgram} from "./programs/TextureProgram";
import {Texture} from "./asserts/Texture";
import {Ambient} from "./scene/lights/Ambient";
import {PointLight} from "./scene/lights/PointLight";
import {BlingPhongProgram} from "./programs/BlingPhongProgram";
import {BlingPhongMaterial} from "./scene/materials/BlingPhongMaterial";


class Main {
    canvas: HTMLCanvasElement;
    // frameBuffer: FrameBuffer;
    // zBuffer: ZBuffer;
    canvasRender: CanvasDrawContext;
    renderContext: RenderContext;

    execute() {
        this.canvas = document.getElementById('webgl') as HTMLCanvasElement;
        this.canvasRender = new CanvasDrawContext(this.canvas);
        this.renderContext = new RenderContext(800, 600);
        this.canvasRender.addBuffer(this.renderContext.frameBuffer);
        this.canvasRender.addBuffer(this.renderContext.zBuffer);
        // this.frameBuffer = new FrameBuffer(800, 600);
        // this.frameBuffer.setClearColor(new Vector4(0, 0, 0, 1));
        // this.frameBuffer.clear();
        // this.zBuffer = new ZBuffer(800, 600);
        // this.zBuffer.setBackground([0]);
        // this.zBuffer.clear();
        // this.canvasRender.addBuffer(this.frameBuffer);
        // this.canvasRender.addBuffer(this.zBuffer);
        // this.drawLineTest5();
        this.testPhongRender();
        this.render();
        // this.drawTriangleTest();
        return this;
    }

    createAmbient(): Ambient {
        const light = new Ambient();
        light.color = new Vector3(1, 1, 1);
        return light;
    }
    createPointLights(): PointLight[] {
        const light1 = new PointLight();
        light1.color = new Vector3(0.5, 0.5, 0.5);
        light1.position = new Vector3(2, 2, 2);
        return [light1];
        // return [];
    }

    async testPhongRender() {
        const objFile = await ObjFile.loadUrl('./obj/african_head/african_head.obj');
        const diffuseTexture = await Texture.createTagImageTexture('./obj/african_head/african_head_diffuse.tga');
        const specularTexture = await Texture.createTagImageTexture('./obj/african_head/african_head_spec.tga');
        const ambient = this.createAmbient();
        const pointLights = this.createPointLights();
        const {center, size} = objFile.box;
        const roundSpeed = 0.1;
        const time = Date.now();

        setInterval(()=> {
            this.renderContext.clear();
            const r = (Date.now() - time) / 1000 * roundSpeed * Math.PI * 2;
            const camera = new OrthographicCamera();
            camera.position = center.added(new Vector3(0, 0, 0));
            pointLights.forEach(light => light.position = center.added(new Vector3(Math.sin(r)*size.z, 2, Math.cos(r)*size.z)));
            // camera.position = center.added(new Vector3(Math.sin(r)*size.z, 0, Math.cos(r)*size.z));
            camera.lookAt(center, new Vector3(0, 1, 0));
            const viewMatrix = camera.getViewMatrix();
            const projectMatrix = camera.getProjectMatrix();
            const mvp = projectMatrix.clone().multiply(viewMatrix);

            const program = new BlingPhongProgram(this.renderContext);
            for (const face of objFile.wrapFaces) {
                program.execute(
                    // @ts-ignore
                    face.vertexInfos.map(info => ({
                        aPosition: info.vertex,
                        aUv: info.uv,
                        aNormal: info.normal,
                    })),
                    {
                        uAmbient: ambient,
                        uPointLights: pointLights,
                        uCameraPoint: camera.position,
                        uSpecularTexture: specularTexture,
                        uDiffuseTexture: diffuseTexture,
                        uMVPMatrix: mvp,
                        uShininess: 30,
                    }
                )
            }

        }, 30);

    }

    async testObjRenderText() {
        const re = await fetch('./obj/african_head/african_head.obj')
        const text = await re.text();
        const objFile = new ObjFile().loadString(text);
        const texture = await Texture.createTagImageTexture('./obj/african_head/african_head_diffuse.tga')
        console.log(objFile);
        const {center, size} = objFile.box;
        const roundSpeed = 0.1;
        const time = Date.now();
        setInterval( () => {
            this.renderContext.clear();
            const r = (Date.now() - time) / 1000 * roundSpeed * Math.PI * 2;
            const camera = new OrthographicCamera();
            camera.position = center.added(new Vector3(Math.sin(r)*size.z, 0, Math.cos(r)*size.z));
            camera.lookAt(center, new Vector3(0, 1, 0));

            // 添加简单关照
            const lightDire = new Vector3(0, 0, -1);

            const viewMatrix = camera.getViewMatrix();
            const projectMatrix = camera.getProjectMatrix();
            const mvp = projectMatrix.clone().multiply(viewMatrix);

            // this.zBuffer.setRange(camera.position.appliedMatrix4(cmvp).z , objFile.box.min.appliedMatrix4(cmvp).z);
            const program = new TextureProgram(this.renderContext);
            for (const face of objFile.faces) {
                // 绘制面
                {
                    let p0 = objFile.vertexes[face.vertexIndexes[0]];
                    let p1 = objFile.vertexes[face.vertexIndexes[1]];
                    let p2 = objFile.vertexes[face.vertexIndexes[2]];
                    let uv0 = objFile.uvs[face.uvIndexes[0]];
                    let uv1 = objFile.uvs[face.uvIndexes[1]];
                    let uv2 = objFile.uvs[face.uvIndexes[2]];
                    const n = p2.subed(p0).cross(p1.subed(p0)).normalize();
                    program.execute([{aPosition: p0, aUv: uv0}, {aPosition: p1, aUv: uv1}, {aPosition: p2, aUv: uv2}],
                        { uMVPMatrix: mvp, uTexture: texture});
                }
            }
        }, 30);
    }
    async testObjRender() {
        const re = await fetch('./obj/african_head/african_head.obj')
        const text = await re.text();
        const objFile = new ObjFile().loadString(text);
        console.log(objFile);
        const {center, size} = objFile.box;
        const roundSpeed = 0.1;
        const time = Date.now();
        setInterval( () => {
            this.renderContext.clear();
            const r = (Date.now() - time) / 1000 * roundSpeed * Math.PI * 2;
            const camera = new OrthographicCamera();
            camera.position = center.added(new Vector3(Math.sin(r)*size.z, 0, Math.cos(r)*size.z));
            camera.lookAt(center, new Vector3(0, 1, 0));

            // 添加简单关照
            const lightDire = new Vector3(0, 0, -1);

            const viewMatrix = camera.getViewMatrix();
            const projectMatrix = camera.getProjectMatrix();
            const mvp = projectMatrix.clone().multiply(viewMatrix);

            // this.zBuffer.setRange(camera.position.appliedMatrix4(cmvp).z , objFile.box.min.appliedMatrix4(cmvp).z);
            const program = new MeshProgram(this.renderContext);
            for (const face of objFile.faces) {
                // 绘制面
                {
                    let p0 = objFile.vertexes[face.vertexIndexes[0]];
                    let p1 = objFile.vertexes[face.vertexIndexes[1]];
                    let p2 = objFile.vertexes[face.vertexIndexes[2]];
                    const n = p2.subed(p0).cross(p1.subed(p0)).normalize();
                    program.execute([{aNormal: n, aPosition: p0}, {aNormal: n, aPosition: p1}, {aNormal: n, aPosition: p2}],
                        {uLightDire: lightDire, uMVPMatrix: mvp});
                }
            }
        }, 30);
    }
    // async testObjAndCamera() {
    //     const re = await fetch('./obj/african_head/african_head.obj')
    //     const text = await re.text();
    //     const objFile = new ObjFile().loadString(text);
    //     console.log(objFile);
    //     const {center, size} = objFile.box;
    //
    //     const camera = new OrthographicCamera();
    //     camera.position = center.added(new Vector3(1, 0, size.z));
    //     camera.lookAt(center, new Vector3(0, 1, 0));
    //
    //
    //
    //     // 添加简单关照
    //     const lightDire = new Vector3(0, 0, -1);
    //
    //     const canvasMatrix = new Matrix4().makeScale(this.frameBuffer.width / 2 , this.frameBuffer.height / -2 , 1).multiply(new Matrix4().makeTranslation(1, -1, 0));
    //     const viewMatrix = camera.getViewMatrix();
    //     const projectMatrix = camera.getProjectMatrix();
    //     const mvp = projectMatrix.clone().multiply(viewMatrix);
    //     const cmvp = canvasMatrix.clone().multiply(projectMatrix).multiply(viewMatrix);
    //
    //
    //     this.zBuffer.setRange(camera.position.appliedMatrix4(cmvp).z , objFile.box.min.appliedMatrix4(cmvp).z);
    //
    //     for (const face of objFile.faces) {
    //         // 绘制面
    //         {
    //             let p0 = objFile.vertexes[face.vertexIndexes[0]];
    //             let p1 = objFile.vertexes[face.vertexIndexes[1]];
    //             let p2 = objFile.vertexes[face.vertexIndexes[2]];
    //             const n = p2.subed(p0).cross(p1.subed(p0)).normalize();
    //
    //             p0 = p0.appliedMatrix4(cmvp);
    //             p1 = p1.appliedMatrix4(cmvp);
    //             p2 = p2.appliedMatrix4(cmvp);
    //
    //             MathUtil.setPointToScreenRange(p0, this.frameBuffer.width, this.frameBuffer.height);
    //             MathUtil.setPointToScreenRange(p1, this.frameBuffer.width, this.frameBuffer.height);
    //             MathUtil.setPointToScreenRange(p2, this.frameBuffer.width, this.frameBuffer.height);
    //
    //             const intensity  = Math.abs(n.dot(lightDire));
    //             if (intensity >= 0) {
    //                 let color = new Vector4(intensity, intensity, intensity);
    //                 DrawTriangleUtil.drawTriangle3D(p0, p1, p2, color, this.frameBuffer, this.zBuffer);
    //             }
    //             // DrawTriangleUtil.drawTriangle(p0, p1, p2, color, this.frameBuffer);
    //         }
    //     }
    // }

    // async testObj() {
    //     const re = await fetch('./obj/african_head/african_head.obj')
    //     const text = await re.text();
    //     const objFile = new ObjFile().loadString(text);
    //     console.log(objFile);
    //     const color: Vector4 = new Vector4(1, 1, 1, 1);
    //     const {center, size} = objFile.box;
    //     const scale = Math.min(this.frameBuffer.width / size.x, this.frameBuffer.height / size.y);
    //     this.zBuffer.setRange(objFile.box.min.z * scale, objFile.box.max.z * scale);
    //
    //     // 添加简单关照
    //     const lightDire = new Vector3(0, 0, -1);
    //
    //     for (const face of objFile.faces) {
    //         // 绘制面
    //         {
    //             let p0 = objFile.vertexes[face.vertexIndexes[0]];
    //             let p1 = objFile.vertexes[face.vertexIndexes[1]];
    //             let p2 = objFile.vertexes[face.vertexIndexes[2]];
    //             const n = p2.subed(p0).cross(p1.subed(p0)).normalize();
    //             p0 = p0.subed(center).multiplied(scale).added(new Vector3(this.frameBuffer.width/2, this.frameBuffer.height/2));
    //             p1 = p1.subed(center).multiplied(scale).added(new Vector3(this.frameBuffer.width/2, this.frameBuffer.height/2));
    //             p2 = p2.subed(center).multiplied(scale).added(new Vector3(this.frameBuffer.width/2, this.frameBuffer.height/2));
    //             MathUtil.setPointToScreenRange(p0, this.frameBuffer.width, this.frameBuffer.height);
    //             MathUtil.setPointToScreenRange(p1, this.frameBuffer.width, this.frameBuffer.height);
    //             MathUtil.setPointToScreenRange(p2, this.frameBuffer.width, this.frameBuffer.height);
    //
    //             const intensity  = n.dot(lightDire);
    //             if (intensity >= 0) {
    //                 let color = new Vector4(intensity, intensity, intensity);
    //                 DrawTriangleUtil.drawTriangle3D(p0, p1, p2, color, this.frameBuffer, this.zBuffer);
    //             }
    //             // DrawTriangleUtil.drawTriangle(p0, p1, p2, color, this.frameBuffer);
    //         }
    //         // 绘制线条
    //         // for(let i = 0; i < 3; i++ ) {
    //         //     let p0 = objFile.vertexes[face.vertexIndexes[i]];
    //         //     let p1 = objFile.vertexes[face.vertexIndexes[(i+1)%3]];
    //         //     // p0 = p0.subed(center).multiplied(scale);
    //         //     // p1 = p1.subed(center).multiplied(scale);
    //         //     p0 = p0.subed(center).multiplied(scale).added(new Vector3(this.frameBuffer.width/2, this.frameBuffer.height/2));
    //         //     p1 = p1.subed(center).multiplied(scale).added(new Vector3(this.frameBuffer.width/2, this.frameBuffer.height/2));
    //         //     MathUtil.setPointToScreenRange(p0, this.frameBuffer.width, this.frameBuffer.height);
    //         //     MathUtil.setPointToScreenRange(p1, this.frameBuffer.width, this.frameBuffer.height);
    //         //     // console.log(p0.x, p0.y, p1.x, p1.y);
    //         //     DrawLineUtil.drawLine(p0, p1, color, this.frameBuffer);
    //         // }
    //     }
    // }
    //
    // drawLineTest1() {
    //     DrawLineUtil.drawLine_1(new Vector2(10, 110), new Vector2(300, 400), new Vector4(1, 0, 0, 1), this.frameBuffer);
    // }
    // drawLineTest2() {
    //     const p0 = new Vector2(300, 300);
    //     const n = 20, r = Math.PI * 2 /n;
    //     for (let i=0; i<n; i++) {
    //         const p1 = p0.added(new Vector2(Math.round(80 * Math.sin(r * i )), Math.round(80 * Math.cos(r * i))));
    //         DrawLineUtil.drawLine_2(p0, p1, new Vector4(1, 0, 0, 1), this.frameBuffer);
    //     }
    // }
    // drawLineTest5() {
    //     const p0 = new Vector2(300, 300);
    //     const n = 20, r = Math.PI * 2 /n;
    //     for (let i=0; i<n; i++) {
    //         const p1 = p0.added(new Vector2(Math.round(80 * Math.sin(r * i )), Math.round(80 * Math.cos(r * i))));
    //         DrawLineUtil.drawLine(p0, p1, new Vector4(1, 0, 0, 1), this.frameBuffer);
    //     }
    // }
    // drawLineTest3() {
    //     DrawLineUtil.drawLine_3(new Vector2(10, 110), new Vector2(300, 400), new Vector4(1, 0, 0, 1), this.frameBuffer);
    // }
    // drawLineTest4() {
    //     DrawLineUtil.drawLine_4(new Vector2(10, 110), new Vector2(300, 400), new Vector4(1, 0, 0, 1), this.frameBuffer);
    // }
    //
    // drawTriangleTest() {
    //     DrawTriangleUtil.drawTriangle(new Vector2(50, 50), new Vector2(100, 200), new Vector2(520, 10), new Vector4(1, 0, 0, 1), this.frameBuffer);
    //     DrawTriangleUtil.drawTriangle(new Vector2(550, 550), new Vector2(500, 300), new Vector2(700, 600), new Vector4(0, 1, 0, 1), this.frameBuffer);
    // }

    render = () => {
        requestAnimationFrame( () => {
            this.canvasRender.render();
            this.render();
        })
    }
}
// @ts-ignore
window.main = new Main().execute();